{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# PDK\n",
    "\n",
    "gdsfactory includes a generic Process Design Kit (PDK), that you can leverage to create your own.\n",
    "\n",
    "A process design kit (PDK) includes:\n",
    "\n",
    "1. LayerStack: different layers with different thickness, z-position, materials and colors.\n",
    "2. Design rule checking deck DRC: Manufacturing rules capturing min feature size, min spacing ... for the process.\n",
    "3. A library of Parametric cells or fixed cells (no parameters).\n",
    "\n",
    "The PDK allows you to register:\n",
    "\n",
    "- `cell` parametric cells that return Components from a ComponentSpec (string, Component, ComponentFactory or dict). Also known as parametric cell functions.\n",
    "- `cross_section` functions that return CrossSection from a CrossSection Spec (string, CrossSection, CrossSectionFactory or dict).\n",
    "- `layers` that return a GDS Layer (gdslayer, gdspurpose) from a string, an int or a Tuple[int, int].\n",
    "\n",
    "\n",
    "Thanks to activating a PDK you can access components, cross_sections or layers using a string, a function or a dict.\n",
    "\n",
    "Depending on the active pdk:\n",
    "\n",
    "- `get_layer` returns a Layer from the registered layers.\n",
    "- `get_component` returns a Component from the registered cells.\n",
    "- `get_cross_section` returns a CrossSection from the registered cross_sections."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## Layers\n",
    "\n",
    "GDS layers are defined as a tuple of two integer numbers `gdslayer/gdspurpose`\n",
    "\n",
    "You can define all the layers using your PDK:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pathlib\n",
    "from functools import partial\n",
    "\n",
    "import pytest\n",
    "from pytest_regressions.data_regression import DataRegressionFixture\n",
    "\n",
    "import gdsfactory as gf\n",
    "from gdsfactory.difftest import difftest\n",
    "from gdsfactory.technology import (\n",
    "    LayerMap,\n",
    ")\n",
    "from gdsfactory.typings import Layer\n",
    "\n",
    "nm = 1e-3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "class LayerMapDemo(LayerMap):\n",
    "    WG: Layer = (1, 0)\n",
    "    DEVREC: Layer = (68, 0)\n",
    "    PORT: Layer = (1, 10)\n",
    "    PORTE: Layer = (1, 11)\n",
    "    LABEL_INSTANCES: Layer = (206, 0)\n",
    "    LABEL_SETTINGS: Layer = (202, 0)\n",
    "    LUMERICAL: Layer = (733, 0)\n",
    "    M1: Layer = (41, 0)\n",
    "    M2: Layer = (45, 0)\n",
    "    M3: Layer = (49, 0)\n",
    "    N: Layer = (20, 0)\n",
    "    NP: Layer = (22, 0)\n",
    "    NPP: Layer = (24, 0)\n",
    "    OXIDE_ETCH: Layer = (6, 0)\n",
    "    P: Layer = (21, 0)\n",
    "    PDPP: Layer = (27, 0)\n",
    "    PP: Layer = (23, 0)\n",
    "    PPP: Layer = (25, 0)\n",
    "    PinRec: Layer = (1, 10)\n",
    "    PinRecM: Layer = (1, 11)\n",
    "    SHALLOWETCH: Layer = (2, 6)\n",
    "    SILICIDE: Layer = (39, 0)\n",
    "    SIM_REGION: Layer = (100, 0)\n",
    "    SITILES: Layer = (190, 0)\n",
    "    SLAB150: Layer = (2, 0)\n",
    "    SLAB150CLAD: Layer = (2, 9)\n",
    "    SLAB90: Layer = (3, 0)\n",
    "    SLAB90CLAD: Layer = (3, 1)\n",
    "    SOURCE: Layer = (110, 0)\n",
    "    TE: Layer = (203, 0)\n",
    "    TEXT: Layer = (66, 0)\n",
    "    TM: Layer = (204, 0)\n",
    "    Text: Layer = (66, 0)\n",
    "    VIA1: Layer = (44, 0)\n",
    "    VIA2: Layer = (43, 0)\n",
    "    VIAC: Layer = (40, 0)\n",
    "    WGCLAD: Layer = (111, 0)\n",
    "    WGN: Layer = (34, 0)\n",
    "    WGclad_material: Layer = (36, 0)\n",
    "\n",
    "\n",
    "LAYER = LayerMapDemo"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "some generic components use some\n",
    "\n",
    "| Layer          | Purpose                                                      |\n",
    "| -------------- | ------------------------------------------------------------ |\n",
    "| LABEL_INSTANCE | for adding instance labels on `gf.read.from_yaml`            |\n",
    "| MTOP           | for top metal routing            |\n",
    "\n",
    "\n",
    "```python\n",
    "class LayersConvenient(LayerMap):\n",
    "    LABEL_INSTANCE: Layer = (66, 0)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "## Cross_Sections\n",
    "\n",
    "You can create a `CrossSection` from scratch or you can customize the cross_section functions in `gf.cross_section`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "from gdsfactory.cross_section import CrossSection, cross_section, xsection\n",
    "from gdsfactory.typings import LayerSpec\n",
    "\n",
    "\n",
    "@xsection\n",
    "def strip2(\n",
    "    width: float = 0.5,\n",
    "    layer: LayerSpec = (2, 0),\n",
    "    radius: float = 10.0,\n",
    "    radius_min: float = 5,\n",
    "    **kwargs,\n",
    ") -> CrossSection:\n",
    "    \"\"\"Return Strip cross_section.\"\"\"\n",
    "    return cross_section(\n",
    "        width=width,\n",
    "        layer=layer,\n",
    "        radius=radius,\n",
    "        radius_min=radius_min,\n",
    "        **kwargs,\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.straight(cross_section=strip2)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "@xsection\n",
    "def pin(\n",
    "    width: float = 0.5,\n",
    "    layer: LayerSpec = \"WG\",\n",
    "    radius: float = 10.0,\n",
    "    radius_min: float = 5,\n",
    "    layer_p: LayerSpec = (21, 0),\n",
    "    layer_n: LayerSpec = (20, 0),\n",
    "    width_p: float = 2,\n",
    "    width_n: float = 2,\n",
    "    offset_p: float = 1,\n",
    "    offset_n: float = -1,\n",
    "    **kwargs,\n",
    ") -> CrossSection:\n",
    "    \"\"\"Return PIN cross_section.\"\"\"\n",
    "    sections = (\n",
    "        gf.Section(layer=layer_p, width=width_p, offset=offset_p),\n",
    "        gf.Section(layer=layer_n, width=width_n, offset=offset_n),\n",
    "    )\n",
    "\n",
    "    return cross_section(\n",
    "        width=width,\n",
    "        layer=layer,\n",
    "        radius=radius,\n",
    "        radius_min=radius_min,\n",
    "        sections=sections,\n",
    "        **kwargs,\n",
    "    )\n",
    "\n",
    "\n",
    "c = gf.components.straight(cross_section=pin)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "@xsection\n",
    "def strip_wide(\n",
    "    width: float = 3,\n",
    "    layer: LayerSpec = (2, 0),\n",
    "    radius: float = 10.0,\n",
    "    radius_min: float = 5,\n",
    "    **kwargs,\n",
    ") -> CrossSection:\n",
    "    \"\"\"Return Strip cross_section.\"\"\"\n",
    "    return cross_section(\n",
    "        width=width,\n",
    "        layer=layer,\n",
    "        radius=radius,\n",
    "        radius_min=radius_min,\n",
    "        **kwargs,\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "strip = gf.cross_section.strip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "cross_sections = dict(strip_wide=strip_wide, pin=pin, strip=strip)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "## Cells\n",
    "\n",
    "Cells are functions that return components. They are parametrized and accept other cells as parameters, so that you can build many levels of complexity. Cells are also known as PCells or parametric cells.\n",
    "\n",
    "You can customize the function's default arguments easily thanks to `functools.partial`\n",
    "Let us now customize the default arguments of a library of cells.\n",
    "\n",
    "For example, you can make some wide MMIs for a particular technology. Let us now assume that the best MMI width you found to be 9um."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "def mmi1x2(width_mmi: float = 9, **kwargs) -> gf.Component:\n",
    "    c = gf.components.mmi1x2(width_mmi=width_mmi)\n",
    "    return c\n",
    "\n",
    "\n",
    "def mmi2x2(width_mmi: float = 9, **kwargs) -> gf.Component:\n",
    "    c = gf.components.mmi2x2(width_mmi=width_mmi)\n",
    "    return c\n",
    "\n",
    "\n",
    "cells = dict(mmi1x2=mmi1x2, mmi2x2=mmi2x2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "You can define a new PDK by creating a function that customizes partial parameters of the generic functions.\n",
    "\n",
    "Assuming this PDK uses layer (41, 0) for the pads (instead of the layer used in the generic pad function):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {},
   "outputs": [],
   "source": [
    "pad_custom_layer = partial(gf.components.pad, layer=(41, 0))\n",
    "c = pad_custom_layer()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16",
   "metadata": {},
   "source": [
    "## PDK\n",
    "\n",
    "You can register Layers, ComponentFactories (Parametric cells) and CrossSectionFactories (cross_sections) into a PDK.\n",
    "Then you can access them through a string after you activate the pdk `PDK.activate()`.\n",
    "\n",
    "### LayerSpec\n",
    "\n",
    "You can access layers of the active PDK using the layer name, a tuple/list of two numbers or a layer from a LayerMap object (such as LAYER.WG)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {},
   "outputs": [],
   "source": [
    "from gdsfactory.generic_tech import get_generic_pdk\n",
    "\n",
    "generic_pdk = get_generic_pdk()\n",
    "\n",
    "pdk1 = gf.Pdk(\n",
    "    name=\"fab1\",\n",
    "    layers=LAYER,\n",
    "    cross_sections=cross_sections,\n",
    "    cells=cells,\n",
    "    layer_views=generic_pdk.layer_views,\n",
    ")\n",
    "pdk1.activate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "pdk1.get_layer(\"WG\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {},
   "outputs": [],
   "source": [
    "pdk1.get_layer((1, 0))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20",
   "metadata": {},
   "source": [
    "### CrossSectionSpec\n",
    "\n",
    "You can access cross_sections of the pdk from the cross_section name, or using a dict to customize the cross-section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "pdk1.get_cross_section(\"pin\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "cross_section_spec_string = \"pin\"\n",
    "c = gf.components.straight(cross_section=cross_section_spec_string)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "xs = gf.get_cross_section(\"pin\", width=2)\n",
    "wg_pin = gf.components.straight(cross_section=xs)\n",
    "wg_pin.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24",
   "metadata": {},
   "source": [
    "### ComponentSpec\n",
    "\n",
    "You can get a component of the active pdk using the cell name (string) or a dict."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = pdk1.get_component(\"mmi1x2\")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = pdk1.get_component(dict(component=\"mmi1x2\", settings=dict(length_mmi=10)))\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27",
   "metadata": {},
   "source": [
    "## Layer Views\n",
    "\n",
    "Layer Views represent the stipples and colors used to render the layers in Klayout.\n",
    "\n",
    "You can define the layer views\n",
    "\n",
    "1. From a Klayout `lyp` (layer properties file). \n",
    "2. From a `yaml` file.\n",
    "3. From scratch, adding all your layers into a class.\n",
    "\n",
    "\n",
    "Now, let us generate the layers definition code from a KLayout `lyp` file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "from gdsfactory.config import PATH\n",
    "import gdsfactory as gf\n",
    "\n",
    "LAYER_VIEWS = gf.technology.LayerViews(PATH.klayout_lyp)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29",
   "metadata": {},
   "source": [
    "LYP files are defined in XML and are only easy to edit in the KLayout Graphical User Interface (GUI).\n",
    "\n",
    "Sometimes you also want to maintain your layer stipple and colors in YAML."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30",
   "metadata": {},
   "outputs": [],
   "source": [
    "LAYER_VIEWS = gf.technology.LayerViews(PATH.klayout_yaml)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31",
   "metadata": {},
   "source": [
    "## PDK\n",
    "\n",
    "To link layout and circuit models to a PDK you need to register them in a PDK.\n",
    "\n",
    "There are also many other pdk settings you can define in the PDK.\n",
    "\n",
    "- `PDK.name` \n",
    "- `PDK.version`\n",
    "- `PDK.cells` functions registered in the PDK to create parametric cells.\n",
    "- `PDK.cross_sections` functions registered in the PDK to create cross_sections.\n",
    "- `PDK.layers` layers registered in the PDK.\n",
    "- `PDK.layer_stack` 3D layer stack registered in the PDK. Useful for device level simulations.\n",
    "- `PDK.layer_views` to define how to render layer colors and styles in KLayout viewer.\n",
    "- `PDK.layer_transitions` to define how to transition between layers automatically.\n",
    "- `PDK.materials_index` to define the materials index for device level simulations.\n",
    "- `PDK.constants` to define PDK constants.\n",
    "- `PDK.connectivity` to define connectivity between layers. For metal connectivity traceability.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from functools import partial\n",
    "from gdsfactory.config import PATH, __version__\n",
    "from gdsfactory.cross_section import cross_sections\n",
    "from gdsfactory.generic_tech.simulation_settings import materials_index\n",
    "from gdsfactory.get_factories import get_cells\n",
    "from gdsfactory.pdk import Pdk\n",
    "from gdsfactory.generic_tech.layer_stack import LAYER_STACK, LAYER\n",
    "from gdsfactory.technology import LayerViews\n",
    "\n",
    "\n",
    "LAYER_VIEWS = LayerViews(filepath=PATH.klayout_yaml)\n",
    "LAYER_CONNECTIVITY = [\n",
    "    (\"NPP\", \"VIAC\", \"M1\"),\n",
    "    (\"PPP\", \"VIAC\", \"M1\"),\n",
    "    (\"M1\", \"VIA1\", \"M2\"),\n",
    "    (\"M2\", \"VIA2\", \"M3\"),\n",
    "]\n",
    "\n",
    "cells = get_cells([gf.components])\n",
    "containers_dict = get_cells([gf.containers])\n",
    "\n",
    "layer_transitions = {\n",
    "    LAYER.WG: partial(gf.c.taper, cross_section=\"strip\", length=10),\n",
    "    (LAYER.WG, LAYER.WGN): \"taper_sc_nc\",\n",
    "    (LAYER.WGN, LAYER.WG): \"taper_nc_sc\",\n",
    "    LAYER.M3: \"taper_electrical\",\n",
    "}\n",
    "\n",
    "class GenericConstants(gf.Constants):\n",
    "    \"\"\"Generic PDK constants.\"\"\"\n",
    "\n",
    "    fiber_input_to_output_spacing: float = 200.0\n",
    "    metal_spacing: float = 10.0\n",
    "    pad_pitch: float = 100.0\n",
    "    pad_size: tuple[float, float] = (80.0, 80.0)\n",
    "    wavelength: float = 1.55\n",
    "\n",
    "\n",
    "PDK = Pdk(\n",
    "    name=\"generic\",\n",
    "    version=__version__,\n",
    "    cells={c: cells[c] for c in cells if c not in containers_dict},\n",
    "    containers=containers_dict,\n",
    "    cross_sections=cross_sections,\n",
    "    layers=LAYER,\n",
    "    layer_stack=LAYER_STACK,\n",
    "    layer_views=LAYER_VIEWS,\n",
    "    layer_transitions=layer_transitions,  # How to transition between layers.\n",
    "    materials_index=materials_index,  # Material index for device level simulations.\n",
    "    constants=GenericConstants(),\n",
    "    connectivity=LAYER_CONNECTIVITY, # For tracing connectivity across layers such as metals.\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33",
   "metadata": {},
   "source": [
    "## Testing PDK cells\n",
    "\n",
    "To make sure all your PDK PCells produce the components that you want, it is important to test your PDK cells.\n",
    "\n",
    "As you write your own cell functions you want to make sure you do not break or produce unwanted regressions later on. You should write tests for this.\n",
    "\n",
    "Make sure you create a `test_components.py` file for pytest to test your PDK. See for example the tests in the [ubc PDK](https://github.com/gdsfactory/ubc)\n",
    "\n",
    "Pytest-regressions automatically creates the CSV and YAML files for you, `gdsfactory.gdsdiff` will store the reference GDS in ref_layouts and check for geometry differences using XOR.\n",
    "\n",
    "gdsfactory is **NOT** backwards compatible, which means that the package will keep improving and evolving.\n",
    "\n",
    "1. To make your work stable you should install a specific version and [pin the version](https://martin-thoma.com/python-requirements/) in your `requirements.txt` or `pyproject.toml` as `gdsfactory==9.24.0` replacing `9.24.0` by whatever version you end up using.\n",
    "2. Before you upgrade gdsfactory to a newer version make sure your tests pass to make sure that things behave as expected\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"This code tests all your cells in the PDK\n",
    "\n",
    "It will test:\n",
    "\n",
    "1. difftest: Will test the GDS geometry of a new GDS compared to a reference.\n",
    "2. settings test: Will compare the settings in YAML with a reference YAML.\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "try:\n",
    "    dirpath = pathlib.Path(__file__).absolute().with_suffix(\".gds\")\n",
    "except Exception:\n",
    "    dirpath = pathlib.Path.cwd()\n",
    "\n",
    "\n",
    "component_names = list(pdk1.cells.keys())\n",
    "factory = pdk1.cells\n",
    "\n",
    "\n",
    "@pytest.fixture(params=component_names, scope=\"function\")\n",
    "def component_name(request) -> str:\n",
    "    return request.param\n",
    "\n",
    "\n",
    "def test_gds(component_name: str) -> None:\n",
    "    \"\"\"Avoid regressions in GDS files. Runs XOR and computes the area.\"\"\"\n",
    "    component = factory[component_name]()\n",
    "    test_name = f\"fabc_{component_name}\"\n",
    "    difftest(component, test_name=test_name, dirpath=dirpath)\n",
    "\n",
    "\n",
    "def test_settings(component_name: str, data_regression: DataRegressionFixture) -> None:\n",
    "    \"\"\"Avoid regressions in component settings and ports.\"\"\"\n",
    "    component = factory[component_name]()\n",
    "    data_regression.check(component.to_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35",
   "metadata": {},
   "source": [
    "## Compare gds files\n",
    "\n",
    "You can use the command line `gf gds diff gds1.gds gds2.gds` to overlay `gds1.gds` and `gds2.gds` files and show them in KLayout.\n",
    "\n",
    "For example, if you changed the mmi1x2 and made it 5um longer by mistake, you could `gf gds diff ref_layouts/mmi1x2.gds run_layouts/mmi1x2.gds` and see the GDS differences in Klayout."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(gf.diff)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37",
   "metadata": {},
   "outputs": [],
   "source": [
    "mmi1 = gf.components.mmi1x2(length_mmi=5)\n",
    "mmi2 = gf.components.mmi1x2(length_mmi=6)\n",
    "gds1 = mmi1.write_gds()\n",
    "gds2 = mmi2.write_gds()\n",
    "gf.diff(gds1, gds2)"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "custom_cell_magics": "kql"
  },
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
